/************************************************************************
  Scheme interpreter
  
  Copyright (C) 2010 Marioly Garza 

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation, either version 3 of the
  License, or (at your option) any later version.
  
  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this program. If not, see
  <http://www.gnu.org/licenses/>.

************************************************************************/

#include "scheme.h"

symbols* scheme::vsymbols(NULL);

object* scheme::evaluate(object* exp, environment* env)
{
    if (util::is_atom(exp))
    {
        if (exp == NULL || util::is_string(exp) || util::is_number(exp))
        {
            return exp;
        }
        else if (vsymbols->get((std::string)*((sstring*)exp)) != NULL)
        {
            return vsymbols->get((std::string)*((sstring*)exp));
        }
        else
        {
            return env->lookup((std::string)*((sstring*)exp));
        }
    }
    else if (util::is<pair>(exp))
    {
        return apply((std::string)*((sstring*)((pair*)exp)->car), ((pair*)exp)->cdr, env);
    }
    else
    {
        throw scheme_exception(util::format("unknown expression type -- %s.", ((std::string)*((sstring*)exp)).c_str()));
    }
}

object* scheme::apply(std::string method, pair* arguments, environment *env)
{

    if (vsymbols->get(method) != NULL && util::is<procedure>(vsymbols->get(method)))
    {
        std::string specials[] = {"if", "define", "quote", "lambda"}; 
        bool eval = array::index_of(specials, 4, method) == -1;

        std::list<object*> args = scheme::va_list(arguments, env, eval);
        return ((native*)vsymbols->get(method))->invoke(args, env);
    }

    else if (util::is<closure>(env->lookup(method)))
    {
        closure* proc = (closure*)env->lookup(method);
        std::list<object*> args = scheme::va_list(arguments, env, true);
        return proc->invoke(args, env);
    }
    else
    {
        throw scheme_exception(util::format("unknown procedure type -- %s", method.c_str()));
    }
}

std::list<object*> scheme::va_list(pair *args, environment *env, bool eval)
{
    if (args == NULL)
        return std::list<object*>();

    object* car = eval ? evaluate(args->car, env) : args->car;
    std::list<object*> lst;
    lst.push_back(car);

    std::list<object*> cdrs = scheme::va_list(args->cdr, env, eval);
    while (cdrs.size() > 0) {
        object* o = cdrs.front();
        lst.push_back(o);
        cdrs.pop_front();
    }
    return lst;
}

int scheme::read_input(std::istream *in, std::ostream *out, environment* env)
{
    std::string exp = "";

    while (!in->eof()) {
        std::string input = "";
        if (exp == "" && out != NULL) *out << "> ";

        getline(*in, input);

        for (int i = 0; i < input.length(); i++)
        {
            exp += input[i];
            int lp = util::count(exp, '(');
            int rp = util::count(exp, ')');
            int qn = util::count(exp, '"');

            if (lp == rp && (qn % 2) == 0 && exp.length() > 1)
            {
                try
                {
                    object *result = scheme::evaluate(parser::parse(exp), env);
                    if (result != NULL && out != NULL)
                        *out << "> " << result->to_string() << std::endl;
                }

                catch (scheme_exception& e) {
                    std::cerr << "exception: " << e.what() << std::endl;
                }
                exp = "";
            }
            else if (rp > lp)
            {
                std::cerr << "unexpected close-parenthesis." << std::endl;
                exp = "";
            }
        }
    }

    if (!exp.empty()) {
        std::cerr << "unexpected end in expression" << std::endl;
        return -1;
    }

    return 0;
}
